Android 系统启动之 Init 进程启动分析一

12/7/2023

本文基于 AOSP android-10.0.0_r41 版本讲解,内核版本 android-goldfish-4.14-gchips

在上一节,我们知道了系统的整个启动流程,对于 Framework 层,我们主要关心应用层的整个流程。

# 1.内核启动 init 进程

内核完成启动后,会通过 run_init_process 函数,启动用户空间的首个进程 init:

// 模拟器内核源码
// init/main.c
static int __ref kernel_init(void *unused)
{   
    // ......

    // ramdisk_execute_command = "/init"
    // 在根文件系统中找可执行文件 /init,如果有就执行它
	if (ramdisk_execute_command) {
		ret = run_init_process(ramdisk_execute_command);
		if (!ret)
			return 0;
		pr_err("Failed to execute %s (error %d)\n",
		       ramdisk_execute_command, ret);
	}


    //......
    
    // 如果 /init 可执行文件没有,就找其他地方的 init 可执行文件,并执行它
    if (execute_command) {
		ret = run_init_process(execute_command);
		if (!ret)
			return 0;
		panic("Requested init %s failed (error %d).",
		      execute_command, ret);
	}
	if (!try_to_run_init_process("/sbin/init") ||
	    !try_to_run_init_process("/etc/init") ||
	    !try_to_run_init_process("/bin/init") ||
	    !try_to_run_init_process("/bin/sh"))
		return 0;   
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

内核会在各个路径寻找 init 可执行文件,并执行它。我手上刷了 AOSP android-10.0.0_r41 的 pixel4 其 init 程序预制在 /init

# 2.init 进程

init 可执行文件对应源码在 system/core/init/main.cpp :

int main(int argc, char** argv) {
#if __has_feature(address_sanitizer)
    __asan_set_error_report_callback(AsanReportCallback);
#endif

    // 可执行文件名为 ueventd
    if (!strcmp(basename(argv[0]), "ueventd")) {
        return ueventd_main(argc, argv);
    }

    if (argc > 1) {
        // 第一个参数为 subcontext
        if (!strcmp(argv[1], "subcontext")) {
            android::base::InitLogging(argv, &android::base::KernelLogger);
            const BuiltinFunctionMap function_map;

            return SubcontextMain(argc, argv, &function_map);
        }

        // 第一个参数为 selinux_setup
        if (!strcmp(argv[1], "selinux_setup")) {
            return SetupSelinux(argv);
        }

        // 第一个参数为 second_stage
        if (!strcmp(argv[1], "second_stage")) {
            return SecondStageMain(argc, argv);
        }
    }

    // 没有参数执行这个函数
    return FirstStageMain(argc, argv);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

这里会检测 main 函数的参数,上一节中我们看到内核启动 init 进程时,没有带参数,所以这里会直接执行到 FirstStageMain

# 3.init 进程第一阶段 FirstStageMain 函数分析

第一阶段 FirstStageMain 函数的工作主要是挂载一些虚拟文件系统,还有挂载两个系统最核心的分区 system, vendor。

这两步都是为了后续的工作做准备。比如设置 selinux 需要从 system, vendor 分区中读取 sepolicy。 SecondStageMain 需要从 system, vendor 分区中读取 property 信息,需要启动系统核心服务。

# 3.1 FirstStageMain 函数第一部分

FirstStageMain 函数很长,我们先看第一部分:

// system/core/init/first_stage_init.cpp

int FirstStageMain(int argc, char** argv) {
    // init crash 时重启引导加载程序
    // 这个函数主要作用将各种信号量,如 SIGABRT,SIGBUS 等的行为设置为 SA_RESTART,一旦监听到这些信号即执行重启系统
    if (REBOOT_BOOTLOADER_ON_PANIC) {
        InstallRebootSignalHandlers();
    }

    boot_clock::time_point start_time = boot_clock::now();

    std::vector<std::pair<std::string, int>> errors;
#define CHECKCALL(x) \
    if (x != 0) errors.emplace_back(#x " failed", errno);
1
2
3
4
5
6
7
8
9
10
11
12
13
14

InstallRebootSignalHandlers() 将各种信号量,如 SIGABRT,SIGBUS 等的行为设置为 SA_RESTART,一旦监听到这些信号即执行重启系统:

void InstallRebootSignalHandlers() {
    // Instead of panic'ing the kernel as is the default behavior when init crashes,
    // we prefer to reboot to bootloader on development builds, as this will prevent
    // boot looping bad configurations and allow both developers and test farms to easily
    // recover.

    // 当init崩溃时,不会像默认行为那样恐慌内核,我们更喜欢在开发构建时重启到bootloader,因为这将防止引导错误的配置,让开发人员和测试人员都可以轻松地进行配置恢复。

    struct sigaction action;
    memset(&action, 0, sizeof(action));
    sigfillset(&action.sa_mask);
    action.sa_handler = [](int signal) {
        // These signal handlers are also caught for processes forked from init, however we do not
        // want them to trigger reboot, so we directly call _exit() for children processes here.

        // 从 init 派生的进程也会捕获这些信号处理程序,但我们不会希望它们触发重启,所以我们在这里直接对子进程调用_exit()
        if (getpid() != 1) {
            _exit(signal);
        }

        // Calling DoReboot() or LOG(FATAL) is not a good option as this is a signal handler.
        // RebootSystem uses syscall() which isn't actually async-signal-safe, but our only option
        // and probably good enough given this is already an error case and only enabled for
        // development builds.

        // 调用DoReboot()或LOG(FATAL)不是一个好的选择,因为这是一个信号处理程序。重启系统使用syscall(),这实际上不是异步信号安全的,但我们唯一的选择,考虑到这已经是一个错误情况,并且只在开发构建时启用,这可能已经足够了。
        InitFatalReboot();
    };
    action.sa_flags = SA_RESTART;
    sigaction(SIGABRT, &action, nullptr);
    sigaction(SIGBUS, &action, nullptr);
    sigaction(SIGFPE, &action, nullptr);
    sigaction(SIGILL, &action, nullptr);
    sigaction(SIGSEGV, &action, nullptr);
#if defined(SIGSTKFLT)
    sigaction(SIGSTKFLT, &action, nullptr);
#endif
    sigaction(SIGSYS, &action, nullptr);
    sigaction(SIGTRAP, &action, nullptr);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

# 3.2 FirstStageMain 函数第二部分

    // Clear the umask.
    umask(0);

    CHECKCALL(clearenv());
    CHECKCALL(setenv("PATH", _PATH_DEFPATH, 1));
    // Get the basic filesystem setup we need put together in the initramdisk
    // on / and then we'll let the rc file figure out the rest.
    CHECKCALL(mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755"));
    CHECKCALL(mkdir("/dev/pts", 0755));
    CHECKCALL(mkdir("/dev/socket", 0755));
    CHECKCALL(mount("devpts", "/dev/pts", "devpts", 0, NULL));
#define MAKE_STR(x) __STRING(x)
    CHECKCALL(mount("proc", "/proc", "proc", 0, "hidepid=2,gid=" MAKE_STR(AID_READPROC)));
#undef MAKE_STR
    // Don't expose the raw commandline to unprivileged processes.
    CHECKCALL(chmod("/proc/cmdline", 0440));
    gid_t groups[] = {AID_READPROC};
    CHECKCALL(setgroups(arraysize(groups), groups));
    CHECKCALL(mount("sysfs", "/sys", "sysfs", 0, NULL));
    CHECKCALL(mount("selinuxfs", "/sys/fs/selinux", "selinuxfs", 0, NULL));

    CHECKCALL(mknod("/dev/kmsg", S_IFCHR | 0600, makedev(1, 11)));

    if constexpr (WORLD_WRITABLE_KMSG) {
        CHECKCALL(mknod("/dev/kmsg_debug", S_IFCHR | 0622, makedev(1, 11)));
    }

    CHECKCALL(mknod("/dev/random", S_IFCHR | 0666, makedev(1, 8)));
    CHECKCALL(mknod("/dev/urandom", S_IFCHR | 0666, makedev(1, 9)));

    // This is needed for log wrapper, which gets called before ueventd runs.
    CHECKCALL(mknod("/dev/ptmx", S_IFCHR | 0666, makedev(5, 2)));
    CHECKCALL(mknod("/dev/null", S_IFCHR | 0666, makedev(1, 3)));

    // These below mounts are done in first stage init so that first stage mount can mount
    // subdirectories of /mnt/{vendor,product}/.  Other mounts, not required by first stage mount,
    // should be done in rc files.
    // Mount staging areas for devices managed by vold
    // See storage config details at http://source.android.com/devices/storage/
    CHECKCALL(mount("tmpfs", "/mnt", "tmpfs", MS_NOEXEC | MS_NOSUID | MS_NODEV,
                    "mode=0755,uid=0,gid=1000"));
    // /mnt/vendor is used to mount vendor-specific partitions that can not be
    // part of the vendor partition, e.g. because they are mounted read-write.
    CHECKCALL(mkdir("/mnt/vendor", 0755));
    // /mnt/product is used to mount product-specific partitions that can not be
    // part of the product partition, e.g. because they are mounted read-write.
    CHECKCALL(mkdir("/mnt/product", 0755));

    // /apex is used to mount APEXes
    CHECKCALL(mount("tmpfs", "/apex", "tmpfs", MS_NOEXEC | MS_NOSUID | MS_NODEV,
                    "mode=0755,uid=0,gid=0"));

    // /debug_ramdisk is used to preserve additional files from the debug ramdisk
    CHECKCALL(mount("tmpfs", "/debug_ramdisk", "tmpfs", MS_NOEXEC | MS_NOSUID | MS_NODEV,
                    "mode=0755,uid=0,gid=0"));
#undef CHECKCALL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56

这部分内容主要是挂载分区,创建设备节点和一些关键目录。

挂载的文件系统主要有四类:

  • tmpfs: 一种虚拟内存文件系统,它会将所有的文件存储在内存中。由于 tmpfs 是驻留在 RAM 的,因此它的内容是不持久的。断电后,tmpfs 的内容就消失了,这也是被称作 tmpfs 的根本原因。
  • devpts: 为伪终端提供了一个标准接口,它的标准挂接点是 /dev/pts。只要 pty(pseudo-tty, 虚拟终端)的主复合设备 /dev/ptmx 被打开,就会在 /dev/pts 下动态的创建一个新的 pty 设备文件。
  • proc: 也是一个虚拟文件系统,它可以看作是内核内部数据结构的接口,通过它我们可以获得系统的信息,同时也能够在运行时修改特定的内核参数。
  • sysfs: 与 proc 文件系统类似,也是一个不占有任何磁盘空间的虚拟文件系统。它通常被挂接在 /sys 目录下。

# 3.3 FirstStageMain 函数第三部分

    SetStdioToDevNull(argv);
1

其实现如下:

// SetStdioToDevNull 函数的作用是将标准输入(stdin)、标准输出(stdout)和标准错误(stderr)重定向到特殊的设备文件 "/dev/null"
void SetStdioToDevNull(char** argv) {
    // Make stdin/stdout/stderr all point to /dev/null.
    int fd = open("/dev/null", O_RDWR);
    if (fd == -1) {
        int saved_errno = errno;
        android::base::InitLogging(argv, &android::base::KernelLogger, InitAborter);
        errno = saved_errno;
        PLOG(FATAL) << "Couldn't open /dev/null";
    }
    dup2(fd, STDIN_FILENO);
    dup2(fd, STDOUT_FILENO);
    dup2(fd, STDERR_FILENO);
    if (fd > STDERR_FILENO) close(fd);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

这里主要是把 stdin stdout stderr 重定向到 /dev/null,也就是说 print 函数是看不到输出的。

# 3.4 FirstStageMain 函数第四部分

    // Now that tmpfs is mounted on /dev and we have /dev/kmsg, we can actually
    // talk to the outside world...

    // Android系统 init 进程启动的时候,log 子系统没有启动起来, 但是我们仍然可以可以使用 logcat -b kernel 看到 init 进程的日志, 这是怎么做到的呢?其实是通过日志重定向来完成的
    InitKernelLogging(argv);
    
    //.....

    LOG(INFO) << "init first stage started!";
1
2
3
4
5
6
7
8
9

Android 系统 init 进程启动的时候,log 子系统没有启动起来, 但是我们仍然可以可以使用 logcat -b kernel 看到 init 进程的日志, 这是怎么做到的呢?其实是通过日志重定向来完成的。

我们先看看 InitKernelLogging 函数的具体实现:

void InitKernelLogging(char** argv) {
    SetFatalRebootTarget();  // 空实现
    android::base::InitLogging(argv, &android::base::KernelLogger, InitAborter);
}
1
2
3
4

接着调用 InitLogging

void InitLogging(char* argv[], LogFunction&& logger, AbortFunction&& aborter) {
  SetLogger(std::forward<LogFunction>(logger));
  SetAborter(std::forward<AbortFunction>(aborter));

  if (gInitialized) {
    return;
  }

  gInitialized = true;

  // Stash the command line for later use. We can use /proc/self/cmdline on
  // Linux to recover this, but we don't have that luxury on the Mac/Windows,
  // and there are a couple of argv[0] variants that are commonly used.
  if (argv != nullptr) {
    SetDefaultTag(basename(argv[0]));
  }

  const char* tags = getenv("ANDROID_LOG_TAGS");
  if (tags == nullptr) {
    return;
  }

    // 根据日志内容,设置 gMinimumLogSeverity 的值
  std::vector<std::string> specs = Split(tags, " ");
  for (size_t i = 0; i < specs.size(); ++i) {
    // "tag-pattern:[vdiwefs]"
    std::string spec(specs[i]);
    if (spec.size() == 3 && StartsWith(spec, "*:")) {
      switch (spec[2]) {
        case 'v':
          gMinimumLogSeverity = VERBOSE;
          continue;
        case 'd':
          gMinimumLogSeverity = DEBUG;
          continue;
        case 'i':
          gMinimumLogSeverity = INFO;
          continue;
        case 'w':
          gMinimumLogSeverity = WARNING;
          continue;
        case 'e':
          gMinimumLogSeverity = ERROR;
          continue;
        case 'f':
          gMinimumLogSeverity = FATAL_WITHOUT_ABORT;
          continue;
        // liblog will even suppress FATAL if you say 's' for silent, but that's
        // crazy!
        case 's':
          gMinimumLogSeverity = FATAL_WITHOUT_ABORT;
          continue;
      }
    }
    LOG(FATAL) << "unsupported '" << spec << "' in ANDROID_LOG_TAGS (" << tags
               << ")";
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
  • 调用 SetLogger 和 SetAborter 来设置两个变量的值
  • 根据日志内容,设置 gMinimumLogSeverity 的值
// 调用过程
SetLogger(std::forward<LogFunction>(logger));
SetAborter(std::forward<AbortFunction>(aborter));

// 具体实现
// system/core/base/logging.cpp
void SetLogger(LogFunction&& logger) {
  std::lock_guard<std::mutex> lock(LoggingLock());
  Logger() = std::move(logger);
}

void SetAborter(AbortFunction&& aborter) {
  std::lock_guard<std::mutex> lock(LoggingLock());
  Aborter() = std::move(aborter);
}

static LogFunction& Logger() {
#ifdef __ANDROID__
  static auto& logger = *new LogFunction(LogdLogger());
#else
  static auto& logger = *new LogFunction(StderrLogger);
#endif
  return logger;
}

static AbortFunction& Aborter() {
  static auto& aborter = *new AbortFunction(DefaultAborter);
  return aborter;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

可以看出,这里就是给两个静态变量赋值。

我们重点关注下 SetLogger 函数设置的 KernelLogger:

#if defined(__linux__)
void KernelLogger(android::base::LogId, android::base::LogSeverity severity,
                  const char* tag, const char*, unsigned int, const char* msg) {
  // clang-format off
  static constexpr int kLogSeverityToKernelLogLevel[] = {
      [android::base::VERBOSE] = 7,              // KERN_DEBUG (there is no verbose kernel log
                                                 //             level)
      [android::base::DEBUG] = 7,                // KERN_DEBUG
      [android::base::INFO] = 6,                 // KERN_INFO
      [android::base::WARNING] = 4,              // KERN_WARNING
      [android::base::ERROR] = 3,                // KERN_ERROR
      [android::base::FATAL_WITHOUT_ABORT] = 2,  // KERN_CRIT
      [android::base::FATAL] = 2,                // KERN_CRIT
  };
  // clang-format on
  static_assert(arraysize(kLogSeverityToKernelLogLevel) == android::base::FATAL + 1,
                "Mismatch in size of kLogSeverityToKernelLogLevel and values in LogSeverity");

  static int klog_fd = OpenKmsg();
  if (klog_fd == -1) return;

  int level = kLogSeverityToKernelLogLevel[severity];

  // The kernel's printk buffer is only 1024 bytes.
  // TODO: should we automatically break up long lines into multiple lines?
  // Or we could log but with something like "..." at the end?
  char buf[1024];
  size_t size = snprintf(buf, sizeof(buf), "<%d>%s: %s\n", level, tag, msg);
  if (size > sizeof(buf)) {
    size = snprintf(buf, sizeof(buf), "<%d>%s: %zu-byte message too long for printk\n",
                    level, tag, size);
  }

  iovec iov[1];
  iov[0].iov_base = buf;
  iov[0].iov_len = size;
  TEMP_FAILURE_RETRY(writev(klog_fd, iov, 1));
}
#endif
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

KernelLogger 是一个函数,其内部实现就是打开 /dev/dmsg,拿到 fd,然后把日志信息格式化一下,接着把日志 write 到前面拿到的 fd 中。

在 init 中通常使用 LOG 宏来写日志:

#define LOG(severity) LOG_TO(DEFAULT, severity)

#define LOG_TO(dest, severity) LOGGING_PREAMBLE(severity) && LOG_STREAM_TO(dest, severity)

#define LOG_STREAM_TO(dest, severity)                                           \
  ::android::base::LogMessage(__FILE__, __LINE__, ::android::base::dest,        \
                              SEVERITY_LAMBDA(severity), _LOG_TAG_INTERNAL, -1) \
      .stream()
1
2
3
4
5
6
7
8

LOG 宏最终会构建一个 LogMessage 对象,在 LogMessage 的析构函数中会调用上面的 KernelLogger 函数写日志到 /dev/dmsg 中。

LogMessage::~LogMessage() {
  // Check severity again. This is duplicate work wrt/ LOG macros, but not LOG_STREAM.
  if (!WOULD_LOG(data_->GetSeverity())) {
    return;
  }

  // Finish constructing the message.
  if (data_->GetError() != -1) {
    data_->GetBuffer() << ": " << strerror(data_->GetError());
  }
  std::string msg(data_->ToString());

  if (data_->GetSeverity() == FATAL) {
#ifdef __ANDROID__
    // Set the bionic abort message early to avoid liblog doing it
    // with the individual lines, so that we get the whole message.
    android_set_abort_message(msg.c_str());
#endif
  }

  {
    // Do the actual logging with the lock held.
    std::lock_guard<std::mutex> lock(LoggingLock());
    if (msg.find('\n') == std::string::npos) {
      LogLine(data_->GetFile(), data_->GetLineNumber(), data_->GetId(), data_->GetSeverity(),
              data_->GetTag(), msg.c_str());
    } else {
      msg += '\n';
      size_t i = 0;
      while (i < msg.size()) {
        size_t nl = msg.find('\n', i);
        msg[nl] = '\0';
        LogLine(data_->GetFile(), data_->GetLineNumber(), data_->GetId(), data_->GetSeverity(),
                data_->GetTag(), &msg[i]);
        // Undo the zero-termination so we can give the complete message to the aborter.
        msg[nl] = '\n';
        i = nl + 1;
      }
    }
  }

  // Abort if necessary.
  if (data_->GetSeverity() == FATAL) {
    Aborter()(msg.c_str());
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46

代码很多,核心就是调用 LogLine 来打印日志:

void LogMessage::LogLine(const char* file, unsigned int line, LogId id, LogSeverity severity,
                         const char* tag, const char* message) {
  if (tag == nullptr) {
    std::lock_guard<std::recursive_mutex> lock(TagLock());
    if (gDefaultTag == nullptr) {
      gDefaultTag = new std::string(getprogname());
    }
    Logger()(id, severity, gDefaultTag->c_str(), file, line, message);
  } else {
    Logger()(id, severity, tag, file, line, message);
  }
}
1
2
3
4
5
6
7
8
9
10
11
12

这里就会通过 logger 函数获取到我们前面设置的 KernelLogger,然后调用它,把日志信息写入到 /dev/dmsg 中。

在析构函数中来打印日志,可以有效的避免打印日志对 init 启动时间的影响。

# 3.5 FirstStageMain 函数第五部分

我们接着看 FirstStageMain 函数第五部分:

    // ......

    /* 
     * 主要作用是去解析 fstab 文件,
     * 然后得到 "/system", "/vendor", "/odm" 三个目录的挂载信息
     * 装载 fstab 中指定的分区
     */
    if 
    if (!DoFirstStageMount()) {
        LOG(FATAL) << "Failed to mount required partitions early ...";
    }
1
2
3
4
5
6
7
8
9
10
11
bool DoFirstStageMount() {
    // Skips first stage mount if we're in recovery mode.
    if (IsRecoveryMode()) {
        LOG(INFO) << "First stage mount skipped (recovery mode)";
        return true;
    }

    // AVB校验
    std::unique_ptr<FirstStageMount> handle = FirstStageMount::Create();
    if (!handle) {
        LOG(ERROR) << "Failed to create FirstStageMount";
        return false;
    }
    return handle->DoFirstStageMount();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

先看第一点 auto fsm = FirstStageMount::Create();,该方法主要用于AVB校验,AVB校验可以去看看 google 文档:

std::unique_ptr<FirstStageMount> FirstStageMount::Create() {
    // 读取 fstab, file system table,里面包含了要挂载的逻辑分区
    auto fstab = ReadFirstStageFstab();
    // 判断device tree(fstabl)中是否有vbmeta/compatible结构,值是android,vbmeta
	// 创建FirstStageMountVBootV1或者FirstStageMountVBootV2实例,取决于	
	// IsDtVbmetaCompatible(fstab)的返回值,如果支持vbmeta,则使用FirstStageMountVBootV2,反之FirstStageMountVBootV1
    if (IsDtVbmetaCompatible(fstab)) {
        return std::make_unique<FirstStageMountVBootV2>(std::move(fstab));
    } else {
        return std::make_unique<FirstStageMountVBootV1>(std::move(fstab));
    }
}
1
2
3
4
5
6
7
8
9
10
11
12

以上主要是创建 V1 或者 V2 版本的 AVB 校验,AV B校验主要是针对分区进行校验,对于要启动的 Android 版本中包含的所有可执行代码和数据,启动时验证均要求在使用前以加密形式对其进行验证。包括内核(从 boot 分区加载)、设备树(从 dtbo 分区加载)、system 分区和 vendor 分区等

  • 对于 boot 和 dtbo 这类仅读取一次的小分区,通常是通过将整个内容加载到内存中,然后计算其哈希值来进行验证
  • 内存装不下的较大分区(如文件系统)可能会使用哈希树;
  • 如果在某个时刻计算出的根哈希值与预期根哈希值不一致,系统便不会使用相应数据,无法启动 Android 系统

我们继续看 handle->DoFirstStageMount() 的实现:

bool FirstStageMount::DoFirstStageMount() {
    if (!IsDmLinearEnabled() && fstab_.empty()) {
        // Nothing to mount.
        LOG(INFO) << "First stage mount skipped (missing/incompatible/empty fstab in device tree)";
        return true;
    }

    if (!InitDevices()) return false;

    if (!CreateLogicalPartitions()) return false;

    if (!MountPartitions()) return false;

    return true;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

这里的逻辑很清晰,初始化设备,创建逻辑分区,最后挂载分区。

里面具体的实现我们就不在分析了,这个不是我们关注的重点。

# 3.6 FirstStageMain 函数第六部分

    // .....

    const char* path = "/system/bin/init";
    const char* args[] = {path, "selinux_setup", nullptr};
    execv(path, const_cast<char**>(args));

    // execv() only returns if an error happened, in which case we
    // panic and never fall through this conditional.
    PLOG(FATAL) << "execv(\"" << path << "\") failed";

    return 1;
}
1
2
3
4
5
6
7
8
9
10
11
12

最后部分,通过 selinux_setup 参数,execv 再次执行 init 可执行文件,进入 init 过程的第二阶段。

# 参考资料